use anyhow::Result;
use serde::Deserialize;
use serde::Serialize;
use serde::Serializer;
use surrealdb::sql::Thing;
use ts_rs::TS;

use crate::model::FudaThing;
use crate::model::indicators::IndicatorGroupKey;
use crate::model::kline::Klt;
use crate::model::malines::MaLineGroupKey;

use super::trade_strategy_data::TradeStrategyData;

/// 指定K线的交易策略，可以结合证券生成交易机器人TradeBot
#[derive(Debug, Serialize, Deserialize, Default, TS, Clone)]
#[ts(export)]
pub struct TradeStrategy {
    /// 唯一标识符，由 strategy.klt.no 组成唯一标识 e.g. strategy.5.1 5分钟K线的1号交易策略
    #[ts(as = "Option<FudaThing>")]
    pub id: Option<Thing>,

    /// 周期类型 Kline Type ,参见[Klt](super::kline::Klt)
    pub klt: Klt,
    /// 序列号
    pub seqno: u32,

    /// 策略名称
    pub name: Option<String>,
    /// 策略描述
    pub desc: Option<String>,
    /// 策略经过回测backtest有效
    pub script_verified: Option<bool>,
    /// 策略脚本, rhai脚本
    pub script: Option<String>,
    /// K线窗口大小，默认为1,策略脚本使用到最近几个K线数据
    pub kl_window: Option<u8>,

    /// 主要用到的指标
    pub indicators: Option<Vec<TradeStrategyIndicator>>,

    /// 主要用到的均线
    pub malines: Option<Vec<TradeStrategyMaLine>>,

    /// 策略是否上线，如果修改将立即下线，只有发布才会修改为上线
    pub is_online: Option<bool>,

    /// 是否生成自动交易指令
    pub is_gen_auto_trade_command: Option<bool>,

    /// 策略数据
    #[serde(serialize_with = "serialize_data")]
    pub data: Option<TradeStrategyData>,

    /// 记录更新日期 HLC
    pub last_updated: Option<String>,
}

fn serialize_data<S>(data: &Option<TradeStrategyData>, serializer: S) -> Result<S::Ok, S::Error>
where
    S: Serializer,
{
    match data {
        Some(dt) => {
           let mut dtmap = serde_json::Map::new();
            dtmap.insert(
                "type".into(),
                serde_json::Value::String(format!("com.fuda.trade.model.TradeStrategyData.{}", dt.tname()))
            );
            match dt {
                TradeStrategyData::Short(data) => {
                    let data_map = serde_json::to_value(data).map_err(|e| serde::ser::Error::custom(e.to_string()))?;
                    dtmap.extend(data_map.as_object().unwrap().clone());
                }
                TradeStrategyData::Long(data) => {
                    let data_map = serde_json::to_value(data).map_err(|e| serde::ser::Error::custom(e.to_string()))?;
                    dtmap.extend(data_map.as_object().unwrap().clone());
                }
            }
            
            serializer.collect_map(dtmap.iter())
        },
        None => serializer.serialize_none(),
    }
}

#[derive(Debug, Serialize, Deserialize, Default, TS, Clone)]
#[ts(export)]
pub struct TradeStrategyIndicator {
    /// indicator group key, e.g. MACD
    pub gk: IndicatorGroupKey,
    /// indicator is used in the strategy script?
    pub is_used: bool,
}

#[derive(Debug, Serialize, Deserialize, Default, TS, Clone)]
#[ts(export)]
pub struct TradeStrategyMaLine {
    /// maline group key, e.g. sma,ema,lsma
    pub gk: MaLineGroupKey,
    /// maline period
    pub period: u8,
    /// maline is used in the strategy script?
    pub is_used: bool,
}

impl TradeStrategy {
    pub fn id_from(klt: &Klt, seqno: u32) -> Result<String> {
        Ok(format!("Tstg.{}.{:04}", klt, seqno))
    }
}

/// A wrapper around Vec<TradeStrategy> to implement Into<zenoh::value::Value>
#[derive(Debug, Serialize, Deserialize)]
pub struct TradeStrategyList(pub Vec<TradeStrategy>);

impl From<Vec<TradeStrategy>> for TradeStrategyList {
    fn from(vec: Vec<TradeStrategy>) -> Self {
        TradeStrategyList(vec)
    }
}

impl Into<zenoh::value::Value> for TradeStrategyList {
    fn into(self) -> zenoh::value::Value {
        match serde_json::to_vec(&self.0) {
            Ok(bytes) => zenoh::value::Value::from(bytes),
            Err(e) => panic!("Failed to serialize TradeStrategyList to JSON: {}", e),
        }
    }
}

#[cfg(test)]
mod test {
    use crate::model::trade::trade_strategy_data;

    use super::*;
    use log::debug;

    #[tokio::test]
    async fn test_trade_strategy_serde() {
        pretty_env_logger::init_timed();
        debug!("debug No more records available; exit the loop");
        println!("print No more records available; exit the loop");

        let strategy = TradeStrategy {
            id: Some(Thing::from(("trade_strategy","SH.STOCK.600519"))),
            is_gen_auto_trade_command: Some(true),
            data: Some(TradeStrategyData::Short (trade_strategy_data::Short{
                volume: 100,
                desc: "short 100 shares".to_string(),
            })),
            ..Default::default()
        };

        let payload = serde_json::to_string(&strategy).unwrap();

        println!("payload: {}", payload)
    }
}
